package tk.mybatis.hot;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.reflection.property.PropertyCopier;
import org.apache.ibatis.session.Configuration;
import org.objenesis.ObjenesisHelper;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;

public class HotConfiguration extends Configuration {
	private static final Log log = LogFactory.getLog(HotConfiguration.class);

	/**
	 * 不可直接实例化
	 */
	private HotConfiguration() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean isResourceLoaded(String resource) {
		return false;
	}

	public static HotConfiguration newInstance(final Configuration configuration) {
		final HotConfiguration hotConfiguration = ObjenesisHelper.newInstance(HotConfiguration.class);
		PropertyCopier.copyBeanProperties(Configuration.class, configuration, hotConfiguration);
		// Field type StrictMap
		ReflectionUtils.doWithFields(Configuration.class, new FieldCallback() {

			@Override
			public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
				accessible(field).set(hotConfiguration, HotStrictMap.newInstance(field.get(configuration)));
			}
		}, new FieldFilter() {

			// 过滤不是 StrictMap 的 Field
			@Override
			public boolean matches(Field field) {
				try {
					return StrictMap.class.isAssignableFrom(accessible(field).get(configuration).getClass());
				} catch (Exception e) {
					return false;
				}
			}
		});

		return hotConfiguration;
	}

	/**
	 * Conveniently render an {@link AccessibleObject} accessible.
	 * <p>
	 * To prevent {@link SecurityException}, this is only done if the argument
	 * object and its declaring class are non-public.
	 *
	 * @param accessible The object to render accessible
	 * @return The argument object rendered accessible
	 */
	public static <T extends AccessibleObject> T accessible(T accessible) {
		if (accessible == null) {
			return null;
		}

		if (accessible instanceof Member) {
			Member member = (Member) accessible;

			if (Modifier.isPublic(member.getModifiers()) && Modifier.isPublic(member.getDeclaringClass().getModifiers())) {

				return accessible;
			}
		}

		// The accessible flag is set to false by default, also for public members.
		if (!accessible.isAccessible()) {
			accessible.setAccessible(true);
		}

		return accessible;
	}

	private static class HotStrictMap<V> extends StrictMap<V> {
		private String name;

		private static final long serialVersionUID = -265642971730560322L;

		/**
		 * 不可直接实例化
		 */
		public HotStrictMap() {
			super(null);
			throw new UnsupportedOperationException();
		}

		void setName(String name) {
			this.name = name;
		}

		// 如果现在状态为刷新，则刷新(先删除后添加)
		public V put(String key, V value) {
			if (log.isDebugEnabled()) {
				log.debug(name + " hot key['" + key + "'].");
			}
			this.remove(key);
			return super.put(key, value);
		}

		@SuppressWarnings("unchecked")
		public static <V> HotStrictMap<V> newInstance(final Object object) {
			final HotStrictMap<V> hsm = ObjenesisHelper.newInstance(HotStrictMap.class);
			PropertyCopier.copyBeanProperties(StrictMap.class, object, hsm);
			// Field name "name"
			ReflectionUtils.doWithFields(StrictMap.class, new FieldCallback() {

				@Override
				public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
					hsm.setName((String) accessible(field).get(object));
				}
			}, new FieldFilter() {

				// 过滤 name 不是 "name" 的 Field
				@Override
				public boolean matches(Field field) {
					try {
						return StrictMap.class == field.getDeclaringClass() && "name".equals(field.getName());
					} catch (Exception e) {
						return false;
					}
				}
			});
			return hsm;
		}
	}

}
